Algunas buenas prácticas de software

library(readr)
library(readxl)
library(curl)
library(dplyr)
library(dbplyr)
library(RPostgreSQL)
library(DBI)
library(dotenv)
library(bit64)

0. Cargando data en local y DB

Esta parte ha sido corrida para crear la base de datos.

Primero, descargamos datos abiertos del OSCE.

read_write_conosce_data <- function(url, name){
    fullpath <- paste0("./data/", name)
    
    if (file.exists(fullpath)){
        cat("reading from file")
        data <- read_csv(fullpath)
    }else{
        tf = tempfile(fileext = ".xlsx")
        curl_download(url, tf)
        data <- read_excel(tf)
        write_csv(data, fullpath)        
    }
    
    return(data)
}

contratos <- read_write_conosce_data("http://conosce.osce.gob.pe/assets/67ae6c4a/reportes/contratos/2020/CONOSCE_CONTRATOS2020_0.xlsx", "contratos_2020.csv")
reading from file

── Column specification ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
cols(
  .default = col_character(),
  CODIGOCONVOCATORIA = col_double(),
  N_COD_CONTRATO = col_double(),
  FECHA_PUBLICACION_CONTRATO = col_datetime(format = ""),
  FECHA_SUSCRIPCION_CONTRATO = col_datetime(format = ""),
  FECHA_VIGENCIA_INICIAL = col_datetime(format = ""),
  FECHA_VIGENCIA_FINAL = col_datetime(format = ""),
  FECHA_VIGENCIA_FIN_ACTUALIZADA = col_datetime(format = ""),
  MONTO_CONTRATADO_TOTAL = col_double(),
  MONTO_CONTRATADO_ITEM = col_double(),
  MONTO_ADICIONAL = col_double(),
  MONTO_REDUCCION = col_double(),
  MONTO_PRORROGA = col_double(),
  MONTO_COMPLEMENTARIO = col_double(),
  NUM_ITEM = col_double()
)
ℹ Use `spec()` for the full column specifications.
adjudicaciones <- read_write_conosce_data("http://conosce.osce.gob.pe/assets/67ae6c4a/reportes/adjudicaciones/2020/CONOSCE_ADJUDICACIONES2020_0.xlsx", "adjudicaciones_2020.csv")
reading from file

── Column specification ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
cols(
  CODIGOENTIDAD = col_character(),
  ENTIDAD_RUC = col_double(),
  CODIGOCONVOCATORIA = col_double(),
  PROCESO = col_character(),
  N_ITEM = col_double(),
  DESCRIPCION_ITEM = col_character(),
  ESTADO_ITEM = col_character(),
  CANTIDAD_ADJUDICADO_ITEM = col_double(),
  MONTO_REFERENCIAL_ITEM = col_double(),
  MONTO_ADJUDICADO_ITEM = col_double(),
  MONEDA = col_character(),
  UNIDAD_MEDIDA = col_character(),
  RUC_PROVEEDOR = col_character(),
  PROVEEDOR = col_character(),
  TIPO_PROVEEDOR = col_character(),
  FECHA_CONVOCATORIA = col_datetime(format = ""),
  FECHA_BUENAPRO = col_datetime(format = ""),
  FECHA_CONSENTIMIENTO_BP = col_datetime(format = "")
)

El archivo .env que les he compartido debe estar en la raiz del proyecto. La libreria dotenv lo leerá desde ahí. Usamos librerías de R que nos dan una interfaz para conectarse e interactuar con la BD.

dotenv::load_dot_env(".env")
pg <-  DBI::dbDriver("PostgreSQL")
con <-RPostgreSQL::dbConnect(
    pg,
    user = Sys.getenv("USER"),
    password= Sys.getenv("PASSWORD"),
    host =Sys.getenv("HOST"),
    port =Sys.getenv("PORT"),
    dbname = Sys.getenv("DBNAME")
)

Copiando datos a base de datos

El listado de las tablas

DBI::dbListTables(con)
[1] "conosce_contratos_v1"      "conosce_adjudicaciones_v1" "conosce_contratos_v2"      "conosce_contratos"        
[5] "conosce_adjudicaciones"   

Copiando uno de los tibbles/dataframes a la base de datos

dplyr::copy_to(
  con,
  contratos,
  name = "conosce_contratos_v1",
  overwrite = FALSE,
  temporary = FALSE,
  analyze = TRUE,
  in_transaction = TRUE
)

El output de esta operación desde la consola de la BD. Vemos el nombre de las columans y el tipo de dato.

R tiene tipos como integer 1L, numeric 1.4, character "hola", logical T, etc. La base de datos manejan tipos análogos.

bbsfunes=> \d conosce_contratos_v1
                              Table "public.conosce_contratos"
             Column             |           Type           | Collation | Nullable | Default 
--------------------------------+--------------------------+-----------+----------+---------
 CODIGOCONVOCATORIA             | double precision         |           |          | 
 N_COD_CONTRATO                 | double precision         |           |          | 
 DESCRIPCION_PROCESO            | text                     |           |          | 
 FECHA_PUBLICACION_CONTRATO     | timestamp with time zone |           |          | 
 FECHA_SUSCRIPCION_CONTRATO     | timestamp with time zone |           |          | 
 FECHA_VIGENCIA_INICIAL         | timestamp with time zone |           |          | 
 FECHA_VIGENCIA_FINAL           | timestamp with time zone |           |          | 
 FECHA_VIGENCIA_FIN_ACTUALIZADA | timestamp with time zone |           |          | 
 CODIGO_CONTRATO                | text                     |           |          | 
 NUM_CONTRATO                   | text                     |           |          | 
 MONTO_CONTRATADO_TOTAL         | double precision         |           |          | 
 MONTO_CONTRATADO_ITEM          | double precision         |           |          | 
 MONTO_ADICIONAL                | double precision         |           |          | 
 MONTO_REDUCCION                | double precision         |           |          | 
 MONTO_PRORROGA                 | double precision         |           |          | 
 MONTO_COMPLEMENTARIO           | double precision         |           |          | 
 URLCONTRATO                    | text                     |           |          | 
 CODIGOENTIDAD                  | text                     |           |          | 
 NUM_ITEM                       | double precision         |           |          | 
 MONEDA                         | text                     |           |          | 
 RUC_CONTRATISTA                | text                     |           |          | 
 RUC_DESTINATARIO_PAGO          | text                     |           |          | 
 TIENERESOLUCION                | text                     |           |          | 

Tipos de datos en Postgresql

tipos <- c("integer", "integer", "text", rep("timestamptz", 5), "text", "text", rep("float", 6), "text", "text", "integer", rep("text", 4))
names(tipos) <- colnames(contratos)

dplyr::copy_to(
  con,
  contratos,
  types = tipos,
  name = "conosce_contratos_v2",
  temporary = FALSE,
  overwrite=T,
  analyze = TRUE,
  in_transaction = FALSE
)
bbsfunes=> \d conosce_contratos
                              Table "public.conosce_contratos"
             Column             |           Type           | Collation | Nullable | Default 
--------------------------------+--------------------------+-----------+----------+---------
 CODIGOCONVOCATORIA             | integer                  |           |          | 
 N_COD_CONTRATO                 | integer                  |           |          | 
 DESCRIPCION_PROCESO            | text                     |           |          | 
 FECHA_PUBLICACION_CONTRATO     | timestamp with time zone |           |          | 
 FECHA_SUSCRIPCION_CONTRATO     | timestamp with time zone |           |          | 
 FECHA_VIGENCIA_INICIAL         | timestamp with time zone |           |          | 
 FECHA_VIGENCIA_FINAL           | timestamp with time zone |           |          | 
 FECHA_VIGENCIA_FIN_ACTUALIZADA | timestamp with time zone |           |          | 
 CODIGO_CONTRATO                | text                     |           |          | 
 NUM_CONTRATO                   | text                     |           |          | 
 MONTO_CONTRATADO_TOTAL         | double precision         |           |          | 
 MONTO_CONTRATADO_ITEM          | double precision         |           |          | 
 MONTO_ADICIONAL                | double precision         |           |          | 
 MONTO_REDUCCION                | double precision         |           |          | 
 MONTO_PRORROGA                 | double precision         |           |          | 
 MONTO_COMPLEMENTARIO           | double precision         |           |          | 
 URLCONTRATO                    | text                     |           |          | 
 CODIGOENTIDAD                  | text                     |           |          | 
 NUM_ITEM                       | integer                  |           |          | 
 MONEDA                         | text                     |           |          | 
 RUC_CONTRATISTA                | text                     |           |          | 
 RUC_DESTINATARIO_PAGO          | text                     |           |          | 
 TIENERESOLUCION                | text                     |           |          | 

¿Por qué podria ser importante asignar el tipo adecuado de dato? Un RUC podríamos representarlo como texto o numérico.

  • velocidad de operación: comparar strings vs comparar enteros

  • cantidad de información: timestamp vs timestamptz vs date

  • espacio ocupado: date [4bytes], timestamptz [8bytes], integer[4bytes], bigint [bytes], double [8bytes]

  • Garantia de integridad de los datos

Si lo haces bien: velocidad, ahorro de espacio, datos representativos. ¿Cuando importa? Cuando tienes mucha data o recursos limitados.

Preguntas:

  • ¿cual es la unidad estadística de los datos?

  • ¿por qué R ha cargado el RUC_CONTRATISTA como texto?

Para modelar bien datos hay que explorarlos primero, entender el proceso que los generó, entender los valores posibles que pueden tomar, y como se asocian entre ellos.

A este análisis se le llama modelamiento entidad-relacion y normalización. Aunque los términos pueden sonar extraños si no se ha leído sobre bases de datos, son los principios detrás del concepto de tidy data del que saca su nombre el tidyverse. Ver Tidy Data de Hadley Wickham, el diseñador del tidyverse.

Una version “ideal”, de nuestros datos, escrita en SQL.

CREATE TABLE conosce_contratos(
    -- se podria hacer una PRIMARY KEY compuesta a partir de NUM_ITEM Y N_COD_CONTRATO,
    id SERIAL PRIMARY KEY,  
    
    -- foreign key:  hace referencia a otra tabla
    -- si es NOT NULL, no puede existir un contrato sin referencia a un proceso de seleccion adjudicado
    CODIGOCONVOCATORIA INTEGER NOT NULL REFERENCES conosce_adjudicaciones(CODIGOCONVOCATORIA),
    
    N_COD_CONTRATO INTEGER NOT NULL,
    DESCRIPCION_PROCESO TEXT NOT NULL,
    FECHA_PUBLICACION_CONTRATO DATE NOT NULL
    
    -- ETC, ...
    
    -- diferencia importante, pero necesitas conocimiento del dominio
    -- los datos estan "sucios" desde su nomenclatura
    RUC_CONTRATISTA BIGINT,
    
    -- de tener los datos completos, esto seria otra tabla
    --RUC_CONTRATISTA BIGINT REFERENCES empresas(RUC),
    
    RNP_CONTRATISTA TEXT NOT NULL
);

-- Indices en Foreign Keys, importante para acelerar busquedas e insertar datos
CREATE INDEX idx_ruc_conosce_contratos ON conosce_contratos(RUC_CONTRATISTA) ;
CREATE INDEX idx_CODIGOCONVOCATORIA_conosce_contratos ON conosce_contratos(CODIGOCONVOCATORIA) ;

Ya que estamos en solo un ejemplo, insertaremos estos datos a la BD sin llaves ni constraints.

contratos %>% 
    mutate(ruc = as.integer64(RUC_CONTRATISTA)) %>% 
    dplyr::copy_to(
      con,
      .,
      types = c(tipos, c("ruc"="bigint")),
      name = "conosce_contratos",
      indexes = list("ruc", "CODIGOCONVOCATORIA", "N_COD_CONTRATO"),
      temporary = FALSE,
      overwrite=T,
      analyze = TRUE,
      in_transaction = FALSE
    )


adjudicaciones_types <- c(
    "text", "bigint", "int", "text", "int", "text", "text", rep("float",3), rep("text", 5), rep("timestamptz", 3)
)
names(adjudicaciones_types) <- colnames(adjudicaciones)


adjudicaciones %>% 
    mutate(ruc= as.integer64(RUC_PROVEEDOR)) %>% 
    dplyr::copy_to(
      con,
      .,
      name = "conosce_adjudicaciones",
      types = c(adjudicaciones_types,     "ruc"="bigint"),
      indexes = list("ENTIDAD_RUC", "CODIGOCONVOCATORIA", "ruc"),
      overwrite = FALSE,
      temporary = FALSE,
      analyze = TRUE,
      in_transaction = FALSE
    )

Inspeccionando tablas en base de datos

tbl(con, "conosce_contratos" ) %>% head(1)
tbl(con, "conosce_adjudicaciones" ) %>% head(1)

1. Integrando datos y computando indicadores

Para analizar rápido los datos, vamos a obviar: - conversión de monedas - tomar en cuenta adicionales, reducciones, etc

contrato <- 
  contratos %>% 
  group_by(RUC_CONTRATISTA, N_COD_CONTRATO) %>% 
  summarise(
    monto_total = sum(MONTO_CONTRATADO_ITEM),
    l = length(unique(MONTO_CONTRATADO_TOTAL)),
    MONTO_CONTRATADO_TOTAL = unique(MONTO_CONTRATADO_TOTAL)[1]
  ) %>% 
  ungroup() %>% 
  
  inner_join(
    distinct(contratos, RUC_CONTRATISTA, N_COD_CONTRATO, CODIGOENTIDAD),
    by = c("RUC_CONTRATISTA", "N_COD_CONTRATO")
  )
`summarise()` regrouping output by 'RUC_CONTRATISTA' (override with `.groups` argument)

Validar: ¿monto_contratado_total es igual al computado sumando items?

Ahora, haciendo las mismas operaciones con dbplyr

contrato_dbplyr <- 
  tbl(con, "conosce_contratos") %>% 
  
  group_by(RUC_CONTRATISTA, N_COD_CONTRATO) %>% 
  summarise(
    monto_total = sum(MONTO_CONTRATADO_ITEM),
    # l = length(unique(MONTO_CONTRATADO_TOTAL)),
    # MONTO_CONTRATADO_TOTAL = unique(MONTO_CONTRATADO_TOTAL)[1]
  ) %>% 
  ungroup() %>% 
  
  inner_join(
    distinct(tbl(con, "conosce_contratos"), RUC_CONTRATISTA, N_COD_CONTRATO, CODIGOENTIDAD),
    by = c("RUC_CONTRATISTA", "N_COD_CONTRATO")
  ) %>% 
  # NOTE: Nuevo
  collect()

Notar que:

Traduccion a SQL

Detrás de escenas, dbplyr traduce el codigo dplyr a SQL

query <- 
  tbl(con, "conosce_contratos") %>% 
  
  group_by(RUC_CONTRATISTA, N_COD_CONTRATO) %>% 
  summarise(
    monto_total = sum(MONTO_CONTRATADO_ITEM),
    # l = length(unique(MONTO_CONTRATADO_TOTAL)),
    # MONTO_CONTRATADO_TOTAL = unique(MONTO_CONTRATADO_TOTAL)[1]
  ) %>%
  
  inner_join(
    distinct(tbl(con, "conosce_contratos"), RUC_CONTRATISTA, N_COD_CONTRATO, CODIGOENTIDAD),
    by = c("RUC_CONTRATISTA", "N_COD_CONTRATO")
  ) 
class(query)
[1] "tbl_PostgreSQLConnection" "tbl_dbi"                  "tbl_sql"                  "tbl_lazy"                 "tbl"                     

¿A que hace referencia tbl_lazy?

sql_render(query)
<SQL> SELECT "LHS"."RUC_CONTRATISTA" AS "RUC_CONTRATISTA", "LHS"."N_COD_CONTRATO" AS "N_COD_CONTRATO", "monto_total", "CODIGOENTIDAD"
FROM (SELECT "RUC_CONTRATISTA", "N_COD_CONTRATO", SUM("MONTO_CONTRATADO_ITEM") AS "monto_total"
FROM "conosce_contratos"
GROUP BY "RUC_CONTRATISTA", "N_COD_CONTRATO") "LHS"
INNER JOIN (SELECT DISTINCT "RUC_CONTRATISTA", "N_COD_CONTRATO", "CODIGOENTIDAD"
FROM "conosce_contratos") "RHS"
ON ("LHS"."RUC_CONTRATISTA" = "RHS"."RUC_CONTRATISTA" AND "LHS"."N_COD_CONTRATO" = "RHS"."N_COD_CONTRATO")

Dandole un formato mas legible:

SELECT 
  -- AS es simplemente renombrar el campo
  "LHS"."RUC_CONTRATISTA" AS "RUC_CONTRATISTA",
  "LHS"."N_COD_CONTRATO" AS "N_COD_CONTRATO",
  "monto_total", 
  "CODIGOENTIDAD"
  
FROM (
  -- conocido como un subquery, o un query anidado dentro de otro
  SELECT 
    "RUC_CONTRATISTA", 
    "N_COD_CONTRATO", 
    -- lo que iria dentro de un mutate ya esta considerado aqui
    SUM("MONTO_CONTRATADO_ITEM") AS "monto_total"
  FROM 
    "conosce_contratos"
  GROUP BY 
    "RUC_CONTRATISTA", 
    "N_COD_CONTRATO"
  ) "LHS"

INNER JOIN (
  SELECT 
    DISTINCT "RUC_CONTRATISTA", "N_COD_CONTRATO", "CODIGOENTIDAD"
  FROM 
    "conosce_contratos"
) "RHS"
  ON  (
    "LHS"."RUC_CONTRATISTA" = "RHS"."RUC_CONTRATISTA" AND
    "LHS"."N_COD_CONTRATO" = "RHS"."N_COD_CONTRATO"
)
dependencia <- 
  contrato %>% 
  group_by(RUC_CONTRATISTA) %>% 
  summarise(
    total_contratista = sum(monto_total),
    n_contratos = n()
  ) %>% 
  inner_join(
    
    contrato %>% 
      group_by(RUC_CONTRATISTA, CODIGOENTIDAD) %>% 
      summarise(
        total_contratista_x_entidad= sum(monto_total)
      ),
    by = "RUC_CONTRATISTA"
  ) %>% 
  mutate(
    proporcion_dependencia = total_contratista_x_entidad/total_contratista
  )
`summarise()` ungrouping output (override with `.groups` argument)
`summarise()` regrouping output by 'RUC_CONTRATISTA' (override with `.groups` argument)

¿Qué sería riesgoso o raro de ver?

dependencia %>% 
  arrange(desc(n_contratos), desc(proporcion_dependencia)) %>% 
  select(proporcion_dependencia, everything()) %>% 
  mutate(proporcion_dependencia = round(proporcion_dependencia, 2))

Un gráfico ayuda a resaltar lo sospechoso.

dependencia %>% 
  ggplot() + 
  geom_point(aes(x=n_contratos, y = proporcion_dependencia)) + 
  labs(
    title="Proporción de dependencia de un contratista con una entidad",
    subtitle ="Cada punto es un contratista x una entidad"
  )

Un poco de interactividad nos ayudará a inspeccionar los puntos sospechosos.

g <- dependencia %>% 
  ggplot() + 
  geom_point(
    aes(x=n_contratos, y = proporcion_dependencia, text = RUC_CONTRATISTA)
  )
Ignoring unknown aesthetics: text
plotly::ggplotly(g, tooltip="text", hover_mode="closest")
Registered S3 method overwritten by 'data.table':
  method           from
  print.data.table     
Registered S3 method overwritten by 'htmlwidgets':
  method           from         
  print.htmlwidget tools:rstudio

Revisando algunos de los procesos para un RUC sospechoso

20515430769 - CORPORACION AGROPECUARIA BERTHA SOCIEDAD ANONIMA CERRADA

filter(contratos, RUC_CONTRATISTA==20515430769) %>% 
  select(DESCRIPCION_PROCESO)

2. Vistas materializadas

Mañana se subirán 10 contratos nuevos, la próxima semana 100. ¿Se corre este script cada vez que se actualice la data? Porque se necesita usar todos los datos para computar las proporciones. Las bases de datos SQL tienen una estructura llamada Materialized Views, donde se crea una “tabla” a partir de las tablas preexistentes, y se guarda en disco, a diferencia de un query que está solo en memoria y debe recomputarse cada vez que se consulta. Usar Vistas materializadas es un tema ya del área conocida como Data Warehousing, donde el énfasis está en el análisis de los datos, una vez garantizada la estabilidad y validez de la base de datos.

CREATE MATERIALIZED VIEW funes_proporcion_dependencia
AS
WITH monto_total_x_contratista AS (
  SELECT 
    "RUC_CONTRATISTA", 
    SUM("MONTO_CONTRATADO_ITEM") AS "monto_total",
    COUNT(DISTINCT("N_COD_CONTRATO")) AS "n_contratos"
  FROM 
    "conosce_contratos"
  GROUP BY 
    "RUC_CONTRATISTA"
), 

  monto_total_contratista_x_entidad AS (
  SELECT 
    "RUC_CONTRATISTA", 
    SUM("MONTO_CONTRATADO_ITEM") as "total_x_entidad",
    "CODIGOENTIDAD"
  FROM 
    "conosce_contratos"
  GROUP BY 
    "RUC_CONTRATISTA", 
    "CODIGOENTIDAD"
)
SELECT
  monto_total_contratista_x_entidad."total_x_entidad" / monto_total_x_contratista."monto_total" AS proporcion_dependencia,
  "RUC_CONTRATISTA",
  "CODIGOENTIDAD",
  n_contratos,
  monto_total
FROM 
  monto_total_contratista_x_entidad
INNER JOIN
  monto_total_x_contratista 
    USING("RUC_CONTRATISTA")
ORDER BY
  n_contratos DESC,
  proporcion_dependencia DESC
;

  
)

Actualización y ahorro en computación

Las vistas materializadas se pueden actualizar con el comando:

REFRESH MATERIALIZED VIEW funes_proporcion_dependencia

Esto recomputa todo el query y lo almacena bajo el mismo nombre.

Ya que la vista es función de ciertas columnas de ciertas tablas, y, posiblemente, de ciertas filas en estas; podría ser que no necesitemos recomputar todo el query, sino solo en aquellos lugares donde ha cambiado. Algunas bases de datos como Amazon Redshift ya lo tienen. En PostgreSQL está en proyecto. El keyword a usar para estas sería INCREMENTALLY

3. Bonus - Window functions en SQL

Usar un pipeline group_by() %>% mutate() %>% ungroup() y traducirlo a SQL.

Necesitamos usar esto cuando queremos calcular los indicadores de riesgo de la red contratista - funcionario. El tiempo de duración y dinero contratado se va acumulando, e importa el orden. Queremos computar un valor para cada instante del tiempo. A esto se le conoce como window function.

LS0tCnRpdGxlOiAiT2pvUMO6YmxpY28gLSBGdW5lczogY2FsY3VsYW5kbyBpbmRpY2Fkb3JlcyBlbiBSIHkgUG9zdGdyZVNRTCIKb3V0cHV0OiAKICBodG1sX25vdGVib29rOgogICAgaGlnaGxpZ2h0OiB0YW5nbwphdXRob3I6ICJHaWFuZnJhbmNvIFJvc3NpIEBnanJvc3NpciIKLS0tCgoKCmBgYHtjc3MsIGVjaG89RkFMU0V9CmgxIHsgcGFkZGluZy10b3A6IDUwcHg7fQpoMiB7IHBhZGRpbmctdG9wOiA0MHB4O30KaDMgeyBwYWRkaW5nLXRvcDogMzBweDt9Cmg0IHsgcGFkZGluZy10b3A6MjBweDt9CmBgYAoKCmBgYHtyIHNldHVwLCBlY2hvID0gRn0Ka25pdHI6Om9wdHNfa25pdCRzZXQocm9vdC5kaXIgPSBycHJvanJvb3Q6OmZpbmRfcnN0dWRpb19yb290X2ZpbGUoKSkKYGBgCgpgYGB7ciwgZXZhbD1GLCBlY2hvID0gRiwgaW5jbHVkZT1GfQppZihGKXsKICAjIEludGVudGFuZG8gcXVlIGFwYXJlemNhIHNpbnRheGlzIGRlIFNRTCBlbiBodG1sX25vdGVib29rCiAgY2F0KHBhc3RlMCgiPHN0eWxlPiIsIHhmdW46OmZpbGVfc3RyaW5nKCIuL2Fzc2V0cy9kZWZhdWx0LmNzcyIpLCAiPC9zdHlsZT4iKSkgIAogIGNhdChwYXN0ZTAoIjxzY3JpcHQ+IiwgeGZ1bjo6ZmlsZV9zdHJpbmcoIi4vYXNzZXRzL2hpZ2hsaWdodC5wYWNrLmpzIiksICI8L3NjcmlwdD4iKSkKfQoKYGBgCgoKIyMjIEFsZ3VuYXMgYnVlbmFzIHByw6FjdGljYXMgZGUgc29mdHdhcmUKCi0gRG90RW52OiBjb252ZW5jaW9uIGRlIHVzYXIgdW4gYXJjaGl2byAuZW52IHBhcmEgZ3VhcmRhciBjb250cmFzZcOxYXMsIHVzdWFyaW9zIHkgb3RyYSBpbmZvcm1hY2nDs24gc2Vuc2libGUuIEVzdGUgYXJjaGl2byBubyBkZWJlIHNlciBjb21taXRlYWRvIGEgR2l0aHViIChwb25lciAuZW52IGVuIC5naXRpZ25vcmUpLiDCv1BvciBxdcOpPyBTZWd1cmlkYWQKCi0gW3JlbnZdKGh0dHBzOi8vcnN0dWRpby5naXRodWIuaW8vcmVudi8pOiAqUiplcHJvZHVjaWJsZSAqRW52Kmlyb25tZW50cy4gU2ltaWxhciBhIHBpcCB5IHJlcXVpcmVtZW50cy50eHQgZW4gUHl0aG9uLiDCv1BvciBxdcOpPyBSZXByb2R1Y2liaWxpZGFkCgoKYGBge3IsIHdhcm5pbmdzPUYsIG1lc3NhZ2U9Rn0KbGlicmFyeShyZWFkcikKbGlicmFyeShyZWFkeGwpCmxpYnJhcnkoY3VybCkKbGlicmFyeShkcGx5cikKbGlicmFyeShkYnBseXIpCmxpYnJhcnkoUlBvc3RncmVTUUwpCmxpYnJhcnkoREJJKQpsaWJyYXJ5KGRvdGVudikKbGlicmFyeShiaXQ2NCkKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KGdncGxvdGx5KQpgYGAKCgojIDAuIENhcmdhbmRvIGRhdGEgZW4gbG9jYWwgeSBEQgoKRXN0YSBwYXJ0ZSBoYSBzaWRvIGNvcnJpZGEgcGFyYSBjcmVhciBsYSBiYXNlIGRlIGRhdG9zLgoKUHJpbWVybywgZGVzY2FyZ2Ftb3MgZGF0b3MgYWJpZXJ0b3MgZGVsIE9TQ0UuCmBgYHtyfQpyZWFkX3dyaXRlX2Nvbm9zY2VfZGF0YSA8LSBmdW5jdGlvbih1cmwsIG5hbWUpewogICAgZnVsbHBhdGggPC0gcGFzdGUwKCIuL2RhdGEvIiwgbmFtZSkKICAgIAogICAgaWYgKGZpbGUuZXhpc3RzKGZ1bGxwYXRoKSl7CiAgICAgICAgY2F0KCJyZWFkaW5nIGZyb20gZmlsZSIpCiAgICAgICAgZGF0YSA8LSByZWFkX2NzdihmdWxscGF0aCkKICAgIH1lbHNlewogICAgICAgIHRmID0gdGVtcGZpbGUoZmlsZWV4dCA9ICIueGxzeCIpCiAgICAgICAgY3VybF9kb3dubG9hZCh1cmwsIHRmKQogICAgICAgIGRhdGEgPC0gcmVhZF9leGNlbCh0ZikKICAgICAgICB3cml0ZV9jc3YoZGF0YSwgZnVsbHBhdGgpICAgICAgICAKICAgIH0KICAgIAogICAgcmV0dXJuKGRhdGEpCn0KCmNvbnRyYXRvcyA8LSByZWFkX3dyaXRlX2Nvbm9zY2VfZGF0YSgiaHR0cDovL2Nvbm9zY2Uub3NjZS5nb2IucGUvYXNzZXRzLzY3YWU2YzRhL3JlcG9ydGVzL2NvbnRyYXRvcy8yMDIwL0NPTk9TQ0VfQ09OVFJBVE9TMjAyMF8wLnhsc3giLCAiY29udHJhdG9zXzIwMjAuY3N2IikKYWRqdWRpY2FjaW9uZXMgPC0gcmVhZF93cml0ZV9jb25vc2NlX2RhdGEoImh0dHA6Ly9jb25vc2NlLm9zY2UuZ29iLnBlL2Fzc2V0cy82N2FlNmM0YS9yZXBvcnRlcy9hZGp1ZGljYWNpb25lcy8yMDIwL0NPTk9TQ0VfQURKVURJQ0FDSU9ORVMyMDIwXzAueGxzeCIsICJhZGp1ZGljYWNpb25lc18yMDIwLmNzdiIpCgpgYGAKCgoKRWwgYXJjaGl2byAuZW52IHF1ZSBsZXMgaGUgY29tcGFydGlkbyBkZWJlIGVzdGFyIGVuIGxhIHJhaXogZGVsIHByb3llY3RvLiBMYSBsaWJyZXJpYSBkb3RlbnYgbG8gbGVlcsOhIGRlc2RlIGFow60uIFVzYW1vcyBsaWJyZXLDrWFzIGRlIFIgcXVlIG5vcyBkYW4gdW5hIGludGVyZmF6IHBhcmEgY29uZWN0YXJzZSBlIGludGVyYWN0dWFyIGNvbiBsYSBCRC4KYGBge3J9CmRvdGVudjo6bG9hZF9kb3RfZW52KCIuZW52IikKcGcgPC0gIERCSTo6ZGJEcml2ZXIoIlBvc3RncmVTUUwiKQpjb24gPC1SUG9zdGdyZVNRTDo6ZGJDb25uZWN0KAogICAgcGcsCiAgICB1c2VyID0gU3lzLmdldGVudigiVVNFUiIpLAogICAgcGFzc3dvcmQ9IFN5cy5nZXRlbnYoIlBBU1NXT1JEIiksCiAgICBob3N0ID1TeXMuZ2V0ZW52KCJIT1NUIiksCiAgICBwb3J0ID1TeXMuZ2V0ZW52KCJQT1JUIiksCiAgICBkYm5hbWUgPSBTeXMuZ2V0ZW52KCJEQk5BTUUiKQopCmBgYAoKCgojIyMgQ29waWFuZG8gZGF0b3MgYSBiYXNlIGRlIGRhdG9zCgpFbCBsaXN0YWRvIGRlIGxhcyB0YWJsYXMKYGBge3J9CkRCSTo6ZGJMaXN0VGFibGVzKGNvbikKYGBgCgpDb3BpYW5kbyB1bm8gZGUgbG9zIHRpYmJsZXMvZGF0YWZyYW1lcyBhIGxhIGJhc2UgZGUgZGF0b3MKYGBge3IsIGV2YWw9Rn0KZHBseXI6OmNvcHlfdG8oCiAgY29uLAogIGNvbnRyYXRvcywKICBuYW1lID0gImNvbm9zY2VfY29udHJhdG9zX3YxIiwKICBvdmVyd3JpdGUgPSBGQUxTRSwKICB0ZW1wb3JhcnkgPSBGQUxTRSwKICBhbmFseXplID0gVFJVRSwKICBpbl90cmFuc2FjdGlvbiA9IFRSVUUKKQpgYGAKCgpFbCBvdXRwdXQgZGUgZXN0YSBvcGVyYWNpw7NuIGRlc2RlIGxhIGNvbnNvbGEgZGUgbGEgQkQuIFZlbW9zIGVsIG5vbWJyZSBkZSBsYXMgY29sdW1hbnMgeSBlbCB0aXBvIGRlIGRhdG8uIAoKUiB0aWVuZSB0aXBvcyBjb21vIGludGVnZXIgYDFMYCwgbnVtZXJpYyBgMS40YCwgY2hhcmFjdGVyIGAiaG9sYSJgLCBsb2dpY2FsIGBUYCwgZXRjLiBMYSBiYXNlIGRlIGRhdG9zIG1hbmVqYW4gdGlwb3MgYW7DoWxvZ29zLiAKYGBgCmJic2Z1bmVzPT4gXGQgY29ub3NjZV9jb250cmF0b3NfdjEKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVGFibGUgInB1YmxpYy5jb25vc2NlX2NvbnRyYXRvcyIKICAgICAgICAgICAgIENvbHVtbiAgICAgICAgICAgICB8ICAgICAgICAgICBUeXBlICAgICAgICAgICB8IENvbGxhdGlvbiB8IE51bGxhYmxlIHwgRGVmYXVsdCAKLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0rLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0rLS0tLS0tLS0tLS0rLS0tLS0tLS0tLSstLS0tLS0tLS0KIENPRElHT0NPTlZPQ0FUT1JJQSAgICAgICAgICAgICB8IGRvdWJsZSBwcmVjaXNpb24gICAgICAgICB8ICAgICAgICAgICB8ICAgICAgICAgIHwgCiBOX0NPRF9DT05UUkFUTyAgICAgICAgICAgICAgICAgfCBkb3VibGUgcHJlY2lzaW9uICAgICAgICAgfCAgICAgICAgICAgfCAgICAgICAgICB8IAogREVTQ1JJUENJT05fUFJPQ0VTTyAgICAgICAgICAgIHwgdGV4dCAgICAgICAgICAgICAgICAgICAgIHwgICAgICAgICAgIHwgICAgICAgICAgfCAKIEZFQ0hBX1BVQkxJQ0FDSU9OX0NPTlRSQVRPICAgICB8IHRpbWVzdGFtcCB3aXRoIHRpbWUgem9uZSB8ICAgICAgICAgICB8ICAgICAgICAgIHwgCiBGRUNIQV9TVVNDUklQQ0lPTl9DT05UUkFUTyAgICAgfCB0aW1lc3RhbXAgd2l0aCB0aW1lIHpvbmUgfCAgICAgICAgICAgfCAgICAgICAgICB8IAogRkVDSEFfVklHRU5DSUFfSU5JQ0lBTCAgICAgICAgIHwgdGltZXN0YW1wIHdpdGggdGltZSB6b25lIHwgICAgICAgICAgIHwgICAgICAgICAgfCAKIEZFQ0hBX1ZJR0VOQ0lBX0ZJTkFMICAgICAgICAgICB8IHRpbWVzdGFtcCB3aXRoIHRpbWUgem9uZSB8ICAgICAgICAgICB8ICAgICAgICAgIHwgCiBGRUNIQV9WSUdFTkNJQV9GSU5fQUNUVUFMSVpBREEgfCB0aW1lc3RhbXAgd2l0aCB0aW1lIHpvbmUgfCAgICAgICAgICAgfCAgICAgICAgICB8IAogQ09ESUdPX0NPTlRSQVRPICAgICAgICAgICAgICAgIHwgdGV4dCAgICAgICAgICAgICAgICAgICAgIHwgICAgICAgICAgIHwgICAgICAgICAgfCAKIE5VTV9DT05UUkFUTyAgICAgICAgICAgICAgICAgICB8IHRleHQgICAgICAgICAgICAgICAgICAgICB8ICAgICAgICAgICB8ICAgICAgICAgIHwgCiBNT05UT19DT05UUkFUQURPX1RPVEFMICAgICAgICAgfCBkb3VibGUgcHJlY2lzaW9uICAgICAgICAgfCAgICAgICAgICAgfCAgICAgICAgICB8IAogTU9OVE9fQ09OVFJBVEFET19JVEVNICAgICAgICAgIHwgZG91YmxlIHByZWNpc2lvbiAgICAgICAgIHwgICAgICAgICAgIHwgICAgICAgICAgfCAKIE1PTlRPX0FESUNJT05BTCAgICAgICAgICAgICAgICB8IGRvdWJsZSBwcmVjaXNpb24gICAgICAgICB8ICAgICAgICAgICB8ICAgICAgICAgIHwgCiBNT05UT19SRURVQ0NJT04gICAgICAgICAgICAgICAgfCBkb3VibGUgcHJlY2lzaW9uICAgICAgICAgfCAgICAgICAgICAgfCAgICAgICAgICB8IAogTU9OVE9fUFJPUlJPR0EgICAgICAgICAgICAgICAgIHwgZG91YmxlIHByZWNpc2lvbiAgICAgICAgIHwgICAgICAgICAgIHwgICAgICAgICAgfCAKIE1PTlRPX0NPTVBMRU1FTlRBUklPICAgICAgICAgICB8IGRvdWJsZSBwcmVjaXNpb24gICAgICAgICB8ICAgICAgICAgICB8ICAgICAgICAgIHwgCiBVUkxDT05UUkFUTyAgICAgICAgICAgICAgICAgICAgfCB0ZXh0ICAgICAgICAgICAgICAgICAgICAgfCAgICAgICAgICAgfCAgICAgICAgICB8IAogQ09ESUdPRU5USURBRCAgICAgICAgICAgICAgICAgIHwgdGV4dCAgICAgICAgICAgICAgICAgICAgIHwgICAgICAgICAgIHwgICAgICAgICAgfCAKIE5VTV9JVEVNICAgICAgICAgICAgICAgICAgICAgICB8IGRvdWJsZSBwcmVjaXNpb24gICAgICAgICB8ICAgICAgICAgICB8ICAgICAgICAgIHwgCiBNT05FREEgICAgICAgICAgICAgICAgICAgICAgICAgfCB0ZXh0ICAgICAgICAgICAgICAgICAgICAgfCAgICAgICAgICAgfCAgICAgICAgICB8IAogUlVDX0NPTlRSQVRJU1RBICAgICAgICAgICAgICAgIHwgdGV4dCAgICAgICAgICAgICAgICAgICAgIHwgICAgICAgICAgIHwgICAgICAgICAgfCAKIFJVQ19ERVNUSU5BVEFSSU9fUEFHTyAgICAgICAgICB8IHRleHQgICAgICAgICAgICAgICAgICAgICB8ICAgICAgICAgICB8ICAgICAgICAgIHwgCiBUSUVORVJFU09MVUNJT04gICAgICAgICAgICAgICAgfCB0ZXh0ICAgICAgICAgICAgICAgICAgICAgfCAgICAgICAgICAgfCAgICAgICAgICB8IAoKYGBgCgoKCiMjIyBUaXBvcyBkZSBkYXRvcyBlbiBQb3N0Z3Jlc3FsCgpgYGB7ciwgZXZhbD1GfQp0aXBvcyA8LSBjKCJpbnRlZ2VyIiwgImludGVnZXIiLCAidGV4dCIsIHJlcCgidGltZXN0YW1wdHoiLCA1KSwgInRleHQiLCAidGV4dCIsIHJlcCgiZmxvYXQiLCA2KSwgInRleHQiLCAidGV4dCIsICJpbnRlZ2VyIiwgcmVwKCJ0ZXh0IiwgNCkpCm5hbWVzKHRpcG9zKSA8LSBjb2xuYW1lcyhjb250cmF0b3MpCgpkcGx5cjo6Y29weV90bygKICBjb24sCiAgY29udHJhdG9zLAogIHR5cGVzID0gdGlwb3MsCiAgbmFtZSA9ICJjb25vc2NlX2NvbnRyYXRvc192MiIsCiAgdGVtcG9yYXJ5ID0gRkFMU0UsCiAgb3ZlcndyaXRlPVQsCiAgYW5hbHl6ZSA9IFRSVUUsCiAgaW5fdHJhbnNhY3Rpb24gPSBGQUxTRQopCmBgYAoKCmBgYApiYnNmdW5lcz0+IFxkIGNvbm9zY2VfY29udHJhdG9zCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFRhYmxlICJwdWJsaWMuY29ub3NjZV9jb250cmF0b3MiCiAgICAgICAgICAgICBDb2x1bW4gICAgICAgICAgICAgfCAgICAgICAgICAgVHlwZSAgICAgICAgICAgfCBDb2xsYXRpb24gfCBOdWxsYWJsZSB8IERlZmF1bHQgCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKy0tLS0tLS0tLS0tKy0tLS0tLS0tLS0rLS0tLS0tLS0tCiBDT0RJR09DT05WT0NBVE9SSUEgICAgICAgICAgICAgfCBpbnRlZ2VyICAgICAgICAgICAgICAgICAgfCAgICAgICAgICAgfCAgICAgICAgICB8IAogTl9DT0RfQ09OVFJBVE8gICAgICAgICAgICAgICAgIHwgaW50ZWdlciAgICAgICAgICAgICAgICAgIHwgICAgICAgICAgIHwgICAgICAgICAgfCAKIERFU0NSSVBDSU9OX1BST0NFU08gICAgICAgICAgICB8IHRleHQgICAgICAgICAgICAgICAgICAgICB8ICAgICAgICAgICB8ICAgICAgICAgIHwgCiBGRUNIQV9QVUJMSUNBQ0lPTl9DT05UUkFUTyAgICAgfCB0aW1lc3RhbXAgd2l0aCB0aW1lIHpvbmUgfCAgICAgICAgICAgfCAgICAgICAgICB8IAogRkVDSEFfU1VTQ1JJUENJT05fQ09OVFJBVE8gICAgIHwgdGltZXN0YW1wIHdpdGggdGltZSB6b25lIHwgICAgICAgICAgIHwgICAgICAgICAgfCAKIEZFQ0hBX1ZJR0VOQ0lBX0lOSUNJQUwgICAgICAgICB8IHRpbWVzdGFtcCB3aXRoIHRpbWUgem9uZSB8ICAgICAgICAgICB8ICAgICAgICAgIHwgCiBGRUNIQV9WSUdFTkNJQV9GSU5BTCAgICAgICAgICAgfCB0aW1lc3RhbXAgd2l0aCB0aW1lIHpvbmUgfCAgICAgICAgICAgfCAgICAgICAgICB8IAogRkVDSEFfVklHRU5DSUFfRklOX0FDVFVBTElaQURBIHwgdGltZXN0YW1wIHdpdGggdGltZSB6b25lIHwgICAgICAgICAgIHwgICAgICAgICAgfCAKIENPRElHT19DT05UUkFUTyAgICAgICAgICAgICAgICB8IHRleHQgICAgICAgICAgICAgICAgICAgICB8ICAgICAgICAgICB8ICAgICAgICAgIHwgCiBOVU1fQ09OVFJBVE8gICAgICAgICAgICAgICAgICAgfCB0ZXh0ICAgICAgICAgICAgICAgICAgICAgfCAgICAgICAgICAgfCAgICAgICAgICB8IAogTU9OVE9fQ09OVFJBVEFET19UT1RBTCAgICAgICAgIHwgZG91YmxlIHByZWNpc2lvbiAgICAgICAgIHwgICAgICAgICAgIHwgICAgICAgICAgfCAKIE1PTlRPX0NPTlRSQVRBRE9fSVRFTSAgICAgICAgICB8IGRvdWJsZSBwcmVjaXNpb24gICAgICAgICB8ICAgICAgICAgICB8ICAgICAgICAgIHwgCiBNT05UT19BRElDSU9OQUwgICAgICAgICAgICAgICAgfCBkb3VibGUgcHJlY2lzaW9uICAgICAgICAgfCAgICAgICAgICAgfCAgICAgICAgICB8IAogTU9OVE9fUkVEVUNDSU9OICAgICAgICAgICAgICAgIHwgZG91YmxlIHByZWNpc2lvbiAgICAgICAgIHwgICAgICAgICAgIHwgICAgICAgICAgfCAKIE1PTlRPX1BST1JST0dBICAgICAgICAgICAgICAgICB8IGRvdWJsZSBwcmVjaXNpb24gICAgICAgICB8ICAgICAgICAgICB8ICAgICAgICAgIHwgCiBNT05UT19DT01QTEVNRU5UQVJJTyAgICAgICAgICAgfCBkb3VibGUgcHJlY2lzaW9uICAgICAgICAgfCAgICAgICAgICAgfCAgICAgICAgICB8IAogVVJMQ09OVFJBVE8gICAgICAgICAgICAgICAgICAgIHwgdGV4dCAgICAgICAgICAgICAgICAgICAgIHwgICAgICAgICAgIHwgICAgICAgICAgfCAKIENPRElHT0VOVElEQUQgICAgICAgICAgICAgICAgICB8IHRleHQgICAgICAgICAgICAgICAgICAgICB8ICAgICAgICAgICB8ICAgICAgICAgIHwgCiBOVU1fSVRFTSAgICAgICAgICAgICAgICAgICAgICAgfCBpbnRlZ2VyICAgICAgICAgICAgICAgICAgfCAgICAgICAgICAgfCAgICAgICAgICB8IAogTU9ORURBICAgICAgICAgICAgICAgICAgICAgICAgIHwgdGV4dCAgICAgICAgICAgICAgICAgICAgIHwgICAgICAgICAgIHwgICAgICAgICAgfCAKIFJVQ19DT05UUkFUSVNUQSAgICAgICAgICAgICAgICB8IHRleHQgICAgICAgICAgICAgICAgICAgICB8ICAgICAgICAgICB8ICAgICAgICAgIHwgCiBSVUNfREVTVElOQVRBUklPX1BBR08gICAgICAgICAgfCB0ZXh0ICAgICAgICAgICAgICAgICAgICAgfCAgICAgICAgICAgfCAgICAgICAgICB8IAogVElFTkVSRVNPTFVDSU9OICAgICAgICAgICAgICAgIHwgdGV4dCAgICAgICAgICAgICAgICAgICAgIHwgICAgICAgICAgIHwgICAgICAgICAgfCAKYGBgCgrCv1BvciBxdcOpIHBvZHJpYSBzZXIgaW1wb3J0YW50ZSBhc2lnbmFyIGVsIHRpcG8gYWRlY3VhZG8gZGUgZGF0bz8gVW4gUlVDIHBvZHLDrWFtb3MgcmVwcmVzZW50YXJsbyBjb21vIHRleHRvIG8gbnVtw6lyaWNvLgoKLSB2ZWxvY2lkYWQgZGUgb3BlcmFjacOzbjogY29tcGFyYXIgc3RyaW5ncyB2cyBjb21wYXJhciBlbnRlcm9zCgotIGNhbnRpZGFkIGRlIGluZm9ybWFjacOzbjogdGltZXN0YW1wIHZzIHRpbWVzdGFtcHR6IHZzIGRhdGUKCi0gZXNwYWNpbyBvY3VwYWRvOiBkYXRlIFs0Ynl0ZXNdLCB0aW1lc3RhbXB0eiBbOGJ5dGVzXSwgaW50ZWdlcls0Ynl0ZXNdLCBiaWdpbnQgW2J5dGVzXSwgZG91YmxlIFs4Ynl0ZXNdCgotIFtHYXJhbnRpYSBkZSBpbnRlZ3JpZGFkIGRlIGxvcyBkYXRvc10oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvQUNJRCkKClNpIGxvIGhhY2VzIGJpZW46IHZlbG9jaWRhZCwgYWhvcnJvIGRlIGVzcGFjaW8sIGRhdG9zIHJlcHJlc2VudGF0aXZvcy4gwr9DdWFuZG8gaW1wb3J0YT8gQ3VhbmRvIHRpZW5lcyBtdWNoYSBkYXRhIG8gcmVjdXJzb3MgbGltaXRhZG9zLgoKClByZWd1bnRhczogCgotIMK/Y3VhbCBlcyBsYSB1bmlkYWQgZXN0YWTDrXN0aWNhIGRlIGxvcyBkYXRvcz8gCgotIMK/cG9yIHF1w6kgUiBoYSBjYXJnYWRvIGVsIFJVQ19DT05UUkFUSVNUQSBjb21vIHRleHRvPwoKUGFyYSBtb2RlbGFyIGJpZW4gZGF0b3MgaGF5IHF1ZSBleHBsb3JhcmxvcyBwcmltZXJvLCBlbnRlbmRlciBlbCBwcm9jZXNvIHF1ZSBsb3MgZ2VuZXLDsywgZW50ZW5kZXIgbG9zIHZhbG9yZXMgcG9zaWJsZXMgcXVlIHB1ZWRlbiB0b21hciwgeSBjb21vIHNlIGFzb2NpYW4gZW50cmUgZWxsb3MuCgpBIGVzdGUgYW7DoWxpc2lzIHNlIGxlIGxsYW1hIFttb2RlbGFtaWVudG8gZW50aWRhZC1yZWxhY2lvbl0oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvRW50aXR5JUUyJTgwJTkzcmVsYXRpb25zaGlwX21vZGVsKSB5IFtub3JtYWxpemFjacOzbl0oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvRGF0YWJhc2Vfbm9ybWFsaXphdGlvbikuIEF1bnF1ZSBsb3MgdMOpcm1pbm9zIHB1ZWRlbiBzb25hciBleHRyYcOxb3Mgc2kgbm8gc2UgaGEgbGXDrWRvIHNvYnJlIGJhc2VzIGRlIGRhdG9zLCBzb24gbG9zIHByaW5jaXBpb3MgZGV0csOhcyBkZWwgY29uY2VwdG8gZGUgKip0aWR5IGRhdGEqKiBkZWwgcXVlIHNhY2Egc3Ugbm9tYnJlIGVsIGB0aWR5dmVyc2VgLiBWZXIgW1RpZHkgRGF0YV0oaHR0cHM6Ly92aXRhLmhhZC5jby5uei9wYXBlcnMvdGlkeS1kYXRhLnBkZikgZGUgSGFkbGV5IFdpY2toYW0sIGVsIGRpc2XDsWFkb3IgZGVsIGB0aWR5dmVyc2VgLgoKIVtdKC4uL2Fzc2V0cy90aWR5X2RhdGFfaGFkbGV5X3NxbC5qcGcpCgpVbmEgdmVyc2lvbiAiaWRlYWwiLCBkZSBudWVzdHJvcyBkYXRvcywgZXNjcml0YSBlbiBTUUwuIAoKYGBge3NxbCwgZXZhbD1GfQpDUkVBVEUgVEFCTEUgY29ub3NjZV9jb250cmF0b3MoCiAgICAtLSBzZSBwb2RyaWEgaGFjZXIgdW5hIFBSSU1BUlkgS0VZIGNvbXB1ZXN0YSBhIHBhcnRpciBkZSBOVU1fSVRFTSBZIE5fQ09EX0NPTlRSQVRPLAogICAgaWQgU0VSSUFMIFBSSU1BUlkgS0VZLCAgCiAgICAKICAgIC0tIGZvcmVpZ24ga2V5OiAgaGFjZSByZWZlcmVuY2lhIGEgb3RyYSB0YWJsYQogICAgLS0gc2kgZXMgTk9UIE5VTEwsIG5vIHB1ZWRlIGV4aXN0aXIgdW4gY29udHJhdG8gc2luIHJlZmVyZW5jaWEgYSB1biBwcm9jZXNvIGRlIHNlbGVjY2lvbiBhZGp1ZGljYWRvCiAgICBDT0RJR09DT05WT0NBVE9SSUEgSU5URUdFUiBOT1QgTlVMTCBSRUZFUkVOQ0VTIGNvbm9zY2VfYWRqdWRpY2FjaW9uZXMoQ09ESUdPQ09OVk9DQVRPUklBKSwKICAgIAogICAgTl9DT0RfQ09OVFJBVE8gSU5URUdFUiBOT1QgTlVMTCwKICAgIERFU0NSSVBDSU9OX1BST0NFU08gVEVYVCBOT1QgTlVMTCwKICAgIEZFQ0hBX1BVQkxJQ0FDSU9OX0NPTlRSQVRPIERBVEUgTk9UIE5VTEwKICAgIAogICAgLS0gRVRDLCAuLi4KICAgIAogICAgLS0gZGlmZXJlbmNpYSBpbXBvcnRhbnRlLCBwZXJvIG5lY2VzaXRhcyBjb25vY2ltaWVudG8gZGVsIGRvbWluaW8KICAgIC0tIGxvcyBkYXRvcyBlc3RhbiAic3VjaW9zIiBkZXNkZSBzdSBub21lbmNsYXR1cmEKICAgIFJVQ19DT05UUkFUSVNUQSBCSUdJTlQsCiAgICAKICAgIC0tIGRlIHRlbmVyIGxvcyBkYXRvcyBjb21wbGV0b3MsIGVzdG8gc2VyaWEgb3RyYSB0YWJsYQogICAgLS1SVUNfQ09OVFJBVElTVEEgQklHSU5UIFJFRkVSRU5DRVMgZW1wcmVzYXMoUlVDKSwKICAgIAogICAgUk5QX0NPTlRSQVRJU1RBIFRFWFQgTk9UIE5VTEwKKTsKCi0tIEluZGljZXMgZW4gRm9yZWlnbiBLZXlzLCBpbXBvcnRhbnRlIHBhcmEgYWNlbGVyYXIgYnVzcXVlZGFzIGUgaW5zZXJ0YXIgZGF0b3MKQ1JFQVRFIElOREVYIGlkeF9ydWNfY29ub3NjZV9jb250cmF0b3MgT04gY29ub3NjZV9jb250cmF0b3MoUlVDX0NPTlRSQVRJU1RBKSA7CkNSRUFURSBJTkRFWCBpZHhfQ09ESUdPQ09OVk9DQVRPUklBX2Nvbm9zY2VfY29udHJhdG9zIE9OIGNvbm9zY2VfY29udHJhdG9zKENPRElHT0NPTlZPQ0FUT1JJQSkgOwpgYGAKCgpZYSBxdWUgZXN0YW1vcyBlbiBzb2xvIHVuIGVqZW1wbG8sIGluc2VydGFyZW1vcyBlc3RvcyBkYXRvcyBhIGxhIEJEIHNpbiBsbGF2ZXMgbmkgY29uc3RyYWludHMuCgpgYGB7ciwgZXZhbD1GfQpjb250cmF0b3MgJT4lIAogICAgbXV0YXRlKHJ1YyA9IGFzLmludGVnZXI2NChSVUNfQ09OVFJBVElTVEEpKSAlPiUgCiAgICBkcGx5cjo6Y29weV90bygKICAgICAgY29uLAogICAgICAuLAogICAgICB0eXBlcyA9IGModGlwb3MsIGMoInJ1YyI9ImJpZ2ludCIpKSwKICAgICAgbmFtZSA9ICJjb25vc2NlX2NvbnRyYXRvcyIsCiAgICAgIGluZGV4ZXMgPSBsaXN0KCJydWMiLCAiQ09ESUdPQ09OVk9DQVRPUklBIiwgIk5fQ09EX0NPTlRSQVRPIiksCiAgICAgIHRlbXBvcmFyeSA9IEZBTFNFLAogICAgICBvdmVyd3JpdGU9VCwKICAgICAgYW5hbHl6ZSA9IFRSVUUsCiAgICAgIGluX3RyYW5zYWN0aW9uID0gRkFMU0UKICAgICkKCgphZGp1ZGljYWNpb25lc190eXBlcyA8LSBjKAogICAgInRleHQiLCAiYmlnaW50IiwgImludCIsICJ0ZXh0IiwgImludCIsICJ0ZXh0IiwgInRleHQiLCByZXAoImZsb2F0IiwzKSwgcmVwKCJ0ZXh0IiwgNSksIHJlcCgidGltZXN0YW1wdHoiLCAzKQopCm5hbWVzKGFkanVkaWNhY2lvbmVzX3R5cGVzKSA8LSBjb2xuYW1lcyhhZGp1ZGljYWNpb25lcykKCgphZGp1ZGljYWNpb25lcyAlPiUgCiAgICBtdXRhdGUocnVjPSBhcy5pbnRlZ2VyNjQoUlVDX1BST1ZFRURPUikpICU+JSAKICAgIGRwbHlyOjpjb3B5X3RvKAogICAgICBjb24sCiAgICAgIC4sCiAgICAgIG5hbWUgPSAiY29ub3NjZV9hZGp1ZGljYWNpb25lcyIsCiAgICAgIHR5cGVzID0gYyhhZGp1ZGljYWNpb25lc190eXBlcywgICAgICJydWMiPSJiaWdpbnQiKSwKICAgICAgaW5kZXhlcyA9IGxpc3QoIkVOVElEQURfUlVDIiwgIkNPRElHT0NPTlZPQ0FUT1JJQSIsICJydWMiKSwKICAgICAgb3ZlcndyaXRlID0gRkFMU0UsCiAgICAgIHRlbXBvcmFyeSA9IEZBTFNFLAogICAgICBhbmFseXplID0gVFJVRSwKICAgICAgaW5fdHJhbnNhY3Rpb24gPSBGQUxTRQogICAgKQpgYGAKCgoKCiMjIyBJbnNwZWNjaW9uYW5kbyB0YWJsYXMgZW4gYmFzZSBkZSBkYXRvcwoKYGBge3J9CnRibChjb24sICJjb25vc2NlX2NvbnRyYXRvcyIgKSAlPiUgaGVhZCgxKQpgYGAKCmBgYHtyfQp0YmwoY29uLCAiY29ub3NjZV9hZGp1ZGljYWNpb25lcyIgKSAlPiUgaGVhZCgxKQpgYGAKCiMgMS4gSW50ZWdyYW5kbyBkYXRvcyB5IGNvbXB1dGFuZG8gaW5kaWNhZG9yZXMKClBhcmEgYW5hbGl6YXIgcsOhcGlkbyBsb3MgZGF0b3MsIHZhbW9zIGEgb2J2aWFyOgogIC0gY29udmVyc2nDs24gZGUgbW9uZWRhcwogIC0gdG9tYXIgZW4gY3VlbnRhIGFkaWNpb25hbGVzLCByZWR1Y2Npb25lcywgZXRjCiAgCgpgYGB7cn0KY29udHJhdG8gPC0gCiAgY29udHJhdG9zICU+JSAKICBncm91cF9ieShSVUNfQ09OVFJBVElTVEEsIE5fQ09EX0NPTlRSQVRPKSAlPiUgCiAgc3VtbWFyaXNlKAogICAgbW9udG9fdG90YWwgPSBzdW0oTU9OVE9fQ09OVFJBVEFET19JVEVNKSwKICAgIGwgPSBsZW5ndGgodW5pcXVlKE1PTlRPX0NPTlRSQVRBRE9fVE9UQUwpKSwKICAgIE1PTlRPX0NPTlRSQVRBRE9fVE9UQUwgPSB1bmlxdWUoTU9OVE9fQ09OVFJBVEFET19UT1RBTClbMV0KICApICU+JSAKICB1bmdyb3VwKCkgJT4lIAogIAogIGlubmVyX2pvaW4oCiAgICBkaXN0aW5jdChjb250cmF0b3MsIFJVQ19DT05UUkFUSVNUQSwgTl9DT0RfQ09OVFJBVE8sIENPRElHT0VOVElEQUQpLAogICAgYnkgPSBjKCJSVUNfQ09OVFJBVElTVEEiLCAiTl9DT0RfQ09OVFJBVE8iKQogICkKYGBgCgpWYWxpZGFyOiDCv21vbnRvX2NvbnRyYXRhZG9fdG90YWwgZXMgaWd1YWwgYWwgY29tcHV0YWRvIHN1bWFuZG8gaXRlbXM/CgoKQWhvcmEsIGhhY2llbmRvIGxhcyBtaXNtYXMgb3BlcmFjaW9uZXMgY29uIGRicGx5cgoKYGBge3J9CmNvbnRyYXRvX2RicGx5ciA8LSAKICB0YmwoY29uLCAiY29ub3NjZV9jb250cmF0b3MiKSAlPiUgCiAgCiAgZ3JvdXBfYnkoUlVDX0NPTlRSQVRJU1RBLCBOX0NPRF9DT05UUkFUTykgJT4lIAogIHN1bW1hcmlzZSgKICAgIG1vbnRvX3RvdGFsID0gc3VtKE1PTlRPX0NPTlRSQVRBRE9fSVRFTSksCiAgICAjIGwgPSBsZW5ndGgodW5pcXVlKE1PTlRPX0NPTlRSQVRBRE9fVE9UQUwpKSwKICAgICMgTU9OVE9fQ09OVFJBVEFET19UT1RBTCA9IHVuaXF1ZShNT05UT19DT05UUkFUQURPX1RPVEFMKVsxXQogICkgJT4lIAogIHVuZ3JvdXAoKSAlPiUgCiAgCiAgaW5uZXJfam9pbigKICAgIGRpc3RpbmN0KHRibChjb24sICJjb25vc2NlX2NvbnRyYXRvcyIpLCBSVUNfQ09OVFJBVElTVEEsIE5fQ09EX0NPTlRSQVRPLCBDT0RJR09FTlRJREFEKSwKICAgIGJ5ID0gYygiUlVDX0NPTlRSQVRJU1RBIiwgIk5fQ09EX0NPTlRSQVRPIikKICApICU+JSAKICAjIE5PVEU6IE51ZXZvCiAgY29sbGVjdCgpCmBgYAoKTm90YXIgcXVlOgoKLSBObyBzb3BvcnRhIGFsZ3VuYXMgb3BlcmFjaW9uZXMKLSBUb2RvIGVsIHByb2Nlc2FtaWVudG8gbG8gaGFjZSBlbiBlbCBzZXJ2aWRvci4gVXRpbCBzaSBubyBzZSBuZWNlc2l0YSB0b2RvIGVsIGRldGFsbGUgZGUgbG9zIGRhdG9zIG8gbm8gc2UgdGllbmUgbWVtb3JpYSkuCi0gU2ludGF4aXMgZXMgZXhhY3RhbWVudGUgbGEgbWlzbWEhIAoKCiMjIyBbVHJhZHVjY2lvbiBhIFNRTF0oaHR0cHM6Ly9kYnBseXIudGlkeXZlcnNlLm9yZy9hcnRpY2xlcy9zcWwtdHJhbnNsYXRpb24uaHRtbCkKCkRldHLDoXMgZGUgZXNjZW5hcywgYGRicGx5cmAgdHJhZHVjZSBlbCBjb2RpZ28gYGRwbHlyYCBhIFNRTApgYGB7cn0KcXVlcnkgPC0gCiAgdGJsKGNvbiwgImNvbm9zY2VfY29udHJhdG9zIikgJT4lIAogIAogIGdyb3VwX2J5KFJVQ19DT05UUkFUSVNUQSwgTl9DT0RfQ09OVFJBVE8pICU+JSAKICBzdW1tYXJpc2UoCiAgICBtb250b190b3RhbCA9IHN1bShNT05UT19DT05UUkFUQURPX0lURU0pLAogICAgIyBsID0gbGVuZ3RoKHVuaXF1ZShNT05UT19DT05UUkFUQURPX1RPVEFMKSksCiAgICAjIE1PTlRPX0NPTlRSQVRBRE9fVE9UQUwgPSB1bmlxdWUoTU9OVE9fQ09OVFJBVEFET19UT1RBTClbMV0KICApICU+JQogIAogIGlubmVyX2pvaW4oCiAgICBkaXN0aW5jdCh0YmwoY29uLCAiY29ub3NjZV9jb250cmF0b3MiKSwgUlVDX0NPTlRSQVRJU1RBLCBOX0NPRF9DT05UUkFUTywgQ09ESUdPRU5USURBRCksCiAgICBieSA9IGMoIlJVQ19DT05UUkFUSVNUQSIsICJOX0NPRF9DT05UUkFUTyIpCiAgKSAKYGBgCgoKYGBge3J9CmNsYXNzKHF1ZXJ5KQpgYGAKwr9BIHF1ZSBoYWNlIHJlZmVyZW5jaWEgYHRibF9sYXp5YD8KCgpgYGB7cn0Kc3FsX3JlbmRlcihxdWVyeSkKYGBgCgpEYW5kb2xlIHVuIGZvcm1hdG8gbWFzIGxlZ2libGU6CmBgYHtzcWwsIGV2YWwgPSBGLCBlY2hvID0gVH0KU0VMRUNUIAogIC0tIEFTIGVzIHNpbXBsZW1lbnRlIHJlbm9tYnJhciBlbCBjYW1wbwogICJMSFMiLiJSVUNfQ09OVFJBVElTVEEiIEFTICJSVUNfQ09OVFJBVElTVEEiLAogICJMSFMiLiJOX0NPRF9DT05UUkFUTyIgQVMgIk5fQ09EX0NPTlRSQVRPIiwKICAibW9udG9fdG90YWwiLCAKICAiQ09ESUdPRU5USURBRCIKICAKRlJPTSAoCiAgLS0gY29ub2NpZG8gY29tbyB1biBzdWJxdWVyeSwgbyB1biBxdWVyeSBhbmlkYWRvIGRlbnRybyBkZSBvdHJvCiAgU0VMRUNUIAogICAgIlJVQ19DT05UUkFUSVNUQSIsIAogICAgIk5fQ09EX0NPTlRSQVRPIiwgCiAgICAtLSBsbyBxdWUgaXJpYSBkZW50cm8gZGUgdW4gbXV0YXRlIHlhIGVzdGEgY29uc2lkZXJhZG8gYXF1aQogICAgU1VNKCJNT05UT19DT05UUkFUQURPX0lURU0iKSBBUyAibW9udG9fdG90YWwiCiAgRlJPTSAKICAgICJjb25vc2NlX2NvbnRyYXRvcyIKICBHUk9VUCBCWSAKICAgICJSVUNfQ09OVFJBVElTVEEiLCAKICAgICJOX0NPRF9DT05UUkFUTyIKICApICJMSFMiCgpJTk5FUiBKT0lOICgKICBTRUxFQ1QgCiAgICBESVNUSU5DVCAiUlVDX0NPTlRSQVRJU1RBIiwgIk5fQ09EX0NPTlRSQVRPIiwgIkNPRElHT0VOVElEQUQiCiAgRlJPTSAKICAgICJjb25vc2NlX2NvbnRyYXRvcyIKKSAiUkhTIgogIE9OICAoCiAgICAiTEhTIi4iUlVDX0NPTlRSQVRJU1RBIiA9ICJSSFMiLiJSVUNfQ09OVFJBVElTVEEiIEFORAogICAgIkxIUyIuIk5fQ09EX0NPTlRSQVRPIiA9ICJSSFMiLiJOX0NPRF9DT05UUkFUTyIKKQpgYGAKCgoKCmBgYHtyfQpkZXBlbmRlbmNpYSA8LSAKICBjb250cmF0byAlPiUgCiAgZ3JvdXBfYnkoUlVDX0NPTlRSQVRJU1RBKSAlPiUgCiAgc3VtbWFyaXNlKAogICAgdG90YWxfY29udHJhdGlzdGEgPSBzdW0obW9udG9fdG90YWwpLAogICAgbl9jb250cmF0b3MgPSBuKCkKICApICU+JSAKICBpbm5lcl9qb2luKAogICAgCiAgICBjb250cmF0byAlPiUgCiAgICAgIGdyb3VwX2J5KFJVQ19DT05UUkFUSVNUQSwgQ09ESUdPRU5USURBRCkgJT4lIAogICAgICBzdW1tYXJpc2UoCiAgICAgICAgdG90YWxfY29udHJhdGlzdGFfeF9lbnRpZGFkPSBzdW0obW9udG9fdG90YWwpCiAgICAgICksCiAgICBieSA9ICJSVUNfQ09OVFJBVElTVEEiCiAgKSAlPiUgCiAgbXV0YXRlKAogICAgcHJvcG9yY2lvbl9kZXBlbmRlbmNpYSA9IHRvdGFsX2NvbnRyYXRpc3RhX3hfZW50aWRhZC90b3RhbF9jb250cmF0aXN0YQogICkKYGBgCsK/UXXDqSBzZXLDrWEgcmllc2dvc28gbyByYXJvIGRlIHZlcj8KCgpgYGB7cn0KZGVwZW5kZW5jaWEgJT4lIAogIGFycmFuZ2UoZGVzYyhuX2NvbnRyYXRvcyksIGRlc2MocHJvcG9yY2lvbl9kZXBlbmRlbmNpYSkpICU+JSAKICBzZWxlY3QocHJvcG9yY2lvbl9kZXBlbmRlbmNpYSwgZXZlcnl0aGluZygpKSAlPiUgCiAgbXV0YXRlKHByb3BvcmNpb25fZGVwZW5kZW5jaWEgPSByb3VuZChwcm9wb3JjaW9uX2RlcGVuZGVuY2lhLCAyKSkKYGBgCgpVbiBncsOhZmljbyBheXVkYSBhIHJlc2FsdGFyIGxvIHNvc3BlY2hvc28uCmBgYHtyfQpkZXBlbmRlbmNpYSAlPiUgCiAgZ2dwbG90KCkgKyAKICBnZW9tX3BvaW50KGFlcyh4PW5fY29udHJhdG9zLCB5ID0gcHJvcG9yY2lvbl9kZXBlbmRlbmNpYSkpICsgCiAgbGFicygKICAgIHRpdGxlPSJQcm9wb3JjacOzbiBkZSBkZXBlbmRlbmNpYSBkZSB1biBjb250cmF0aXN0YSBjb24gdW5hIGVudGlkYWQiLAogICAgc3VidGl0bGUgPSJDYWRhIHB1bnRvIGVzIHVuIGNvbnRyYXRpc3RhIHggdW5hIGVudGlkYWQiCiAgKQpgYGAKVW4gcG9jbyBkZSBpbnRlcmFjdGl2aWRhZCBub3MgYXl1ZGFyw6EgYSBpbnNwZWNjaW9uYXIgbG9zIHB1bnRvcyBzb3NwZWNob3Nvcy4KYGBge3J9CmcgPC0gZGVwZW5kZW5jaWEgJT4lIAogIGdncGxvdCgpICsgCiAgZ2VvbV9wb2ludCgKICAgIGFlcyh4PW5fY29udHJhdG9zLCB5ID0gcHJvcG9yY2lvbl9kZXBlbmRlbmNpYSwgdGV4dCA9IFJVQ19DT05UUkFUSVNUQSkKICApCgpwbG90bHk6OmdncGxvdGx5KGcsIHRvb2x0aXA9InRleHQiLCBob3Zlcl9tb2RlPSJjbG9zZXN0IikKYGBgCgpSZXZpc2FuZG8gYWxndW5vcyBkZSBsb3MgcHJvY2Vzb3MgcGFyYSB1biBSVUMgc29zcGVjaG9zbwoKMjA1MTU0MzA3NjkgLSBDT1JQT1JBQ0lPTiBBR1JPUEVDVUFSSUEgQkVSVEhBIFNPQ0lFREFEIEFOT05JTUEgQ0VSUkFEQQoKYGBge3J9CmZpbHRlcihjb250cmF0b3MsIFJVQ19DT05UUkFUSVNUQT09MjA1MTU0MzA3NjkpICU+JSAKICBzZWxlY3QoREVTQ1JJUENJT05fUFJPQ0VTTykKYGBgCgoKCgojIDIuIFZpc3RhcyBtYXRlcmlhbGl6YWRhcwoKTWHDsWFuYSBzZSBzdWJpcsOhbiAxMCBjb250cmF0b3MgbnVldm9zLCBsYSBwcsOzeGltYSBzZW1hbmEgMTAwLiDCv1NlIGNvcnJlIGVzdGUgc2NyaXB0IGNhZGEgdmV6IHF1ZSBzZSBhY3R1YWxpY2UgbGEgZGF0YT8gUG9ycXVlIHNlIG5lY2VzaXRhIHVzYXIgdG9kb3MgbG9zIGRhdG9zIHBhcmEgY29tcHV0YXIgbGFzIHByb3BvcmNpb25lcy4gTGFzIGJhc2VzIGRlIGRhdG9zIFNRTCB0aWVuZW4gdW5hIGVzdHJ1Y3R1cmEgbGxhbWFkYSBNYXRlcmlhbGl6ZWQgVmlld3MsIGRvbmRlIHNlIGNyZWEgdW5hICJ0YWJsYSIgYSBwYXJ0aXIgZGUgbGFzIHRhYmxhcyBwcmVleGlzdGVudGVzLCB5IHNlIGd1YXJkYSBlbiBkaXNjbywgYSBkaWZlcmVuY2lhIGRlIHVuIHF1ZXJ5IHF1ZSBlc3TDoSBzb2xvIGVuIG1lbW9yaWEgeSBkZWJlIHJlY29tcHV0YXJzZSBjYWRhIHZleiBxdWUgc2UgY29uc3VsdGEuIFVzYXIgVmlzdGFzIG1hdGVyaWFsaXphZGFzIGVzIHVuIHRlbWEgeWEgZGVsIMOhcmVhIGNvbm9jaWRhIGNvbW8gKkRhdGEgV2FyZWhvdXNpbmcqLCBkb25kZSBlbCDDqW5mYXNpcyBlc3TDoSBlbiBlbCBhbsOhbGlzaXMgZGUgbG9zIGRhdG9zLCB1bmEgdmV6IGdhcmFudGl6YWRhIGxhIGVzdGFiaWxpZGFkIHkgdmFsaWRleiBkZSBsYSAqYmFzZSogZGUgZGF0b3MuCgpgYGB7c3FsLCBldmFsPUZ9CkNSRUFURSBNQVRFUklBTElaRUQgVklFVyBmdW5lc19wcm9wb3JjaW9uX2RlcGVuZGVuY2lhCkFTCldJVEggbW9udG9fdG90YWxfeF9jb250cmF0aXN0YSBBUyAoCiAgU0VMRUNUIAogICAgIlJVQ19DT05UUkFUSVNUQSIsIAogICAgU1VNKCJNT05UT19DT05UUkFUQURPX0lURU0iKSBBUyAibW9udG9fdG90YWwiLAogICAgQ09VTlQoRElTVElOQ1QoIk5fQ09EX0NPTlRSQVRPIikpIEFTICJuX2NvbnRyYXRvcyIKICBGUk9NIAogICAgImNvbm9zY2VfY29udHJhdG9zIgogIEdST1VQIEJZIAogICAgIlJVQ19DT05UUkFUSVNUQSIKKSwgCgogIG1vbnRvX3RvdGFsX2NvbnRyYXRpc3RhX3hfZW50aWRhZCBBUyAoCiAgU0VMRUNUIAogICAgIlJVQ19DT05UUkFUSVNUQSIsIAogICAgU1VNKCJNT05UT19DT05UUkFUQURPX0lURU0iKSBhcyAidG90YWxfeF9lbnRpZGFkIiwKICAgICJDT0RJR09FTlRJREFEIgogIEZST00gCiAgICAiY29ub3NjZV9jb250cmF0b3MiCiAgR1JPVVAgQlkgCiAgICAiUlVDX0NPTlRSQVRJU1RBIiwgCiAgICAiQ09ESUdPRU5USURBRCIKKQpTRUxFQ1QKICBtb250b190b3RhbF9jb250cmF0aXN0YV94X2VudGlkYWQuInRvdGFsX3hfZW50aWRhZCIgLyBtb250b190b3RhbF94X2NvbnRyYXRpc3RhLiJtb250b190b3RhbCIgQVMgcHJvcG9yY2lvbl9kZXBlbmRlbmNpYSwKICAiUlVDX0NPTlRSQVRJU1RBIiwKICAiQ09ESUdPRU5USURBRCIsCiAgbl9jb250cmF0b3MsCiAgbW9udG9fdG90YWwKRlJPTSAKICBtb250b190b3RhbF9jb250cmF0aXN0YV94X2VudGlkYWQKSU5ORVIgSk9JTgogIG1vbnRvX3RvdGFsX3hfY29udHJhdGlzdGEgCiAgICBVU0lORygiUlVDX0NPTlRSQVRJU1RBIikKT1JERVIgQlkKICBuX2NvbnRyYXRvcyBERVNDLAogIHByb3BvcmNpb25fZGVwZW5kZW5jaWEgREVTQwo7CgogIAopCmBgYAoKYGBge3J9CnRibChjb24sICJmdW5lc19wcm9wb3JjaW9uX2RlcGVuZGVuY2lhIikgJT4lIGhlYWQoKQpgYGAKCgojIyMgQWN0dWFsaXphY2nDs24geSBhaG9ycm8gZW4gY29tcHV0YWNpw7NuCgpMYXMgdmlzdGFzIG1hdGVyaWFsaXphZGFzIHNlIHB1ZWRlbiBhY3R1YWxpemFyIGNvbiBlbCBjb21hbmRvOgpgYGB7c3FsfQpSRUZSRVNIIE1BVEVSSUFMSVpFRCBWSUVXIGZ1bmVzX3Byb3BvcmNpb25fZGVwZW5kZW5jaWEKYGBgCgpFc3RvIHJlY29tcHV0YSB0b2RvIGVsIHF1ZXJ5IHkgbG8gYWxtYWNlbmEgYmFqbyBlbCBtaXNtbyBub21icmUuCgpZYSBxdWUgbGEgdmlzdGEgZXMgZnVuY2nDs24gZGUgY2llcnRhcyBjb2x1bW5hcyBkZSBjaWVydGFzIHRhYmxhcywgeSwgcG9zaWJsZW1lbnRlLCBkZSBjaWVydGFzIGZpbGFzIGVuIGVzdGFzOyBwb2Ryw61hIHNlciBxdWUgbm8gbmVjZXNpdGVtb3MgcmVjb21wdXRhciAqdG9kbyogZWwgcXVlcnksIHNpbm8gc29sbyBlbiBhcXVlbGxvcyBsdWdhcmVzIGRvbmRlIGhhIGNhbWJpYWRvLiBBbGd1bmFzIGJhc2VzIGRlIGRhdG9zIGNvbW8gQW1hem9uIFtSZWRzaGlmdF0oaHR0cHM6Ly9kb2NzLmF3cy5hbWF6b24uY29tL3JlZHNoaWZ0L2xhdGVzdC9kZy9tYXRlcmlhbGl6ZWQtdmlldy1yZWZyZXNoLXNxbC1jb21tYW5kLmh0bWwpIHlhIGxvIHRpZW5lbi4gRW4gUG9zdGdyZVNRTCBlc3TDoSBlbiBbcHJveWVjdG9dKChodHRwczovL3dpa2kucG9zdGdyZXNxbC5vcmcvd2lraS9JbmNyZW1lbnRhbF9WaWV3X01haW50ZW5hbmNlKSkuIEVsIGtleXdvcmQgYSB1c2FyIHBhcmEgZXN0YXMgc2Vyw61hIGBJTkNSRU1FTlRBTExZYAoKCgoKIyAzLiBCb251cyAtIFdpbmRvdyBmdW5jdGlvbnMgZW4gU1FMCgpVc2FyIHVuIHBpcGVsaW5lIGBncm91cF9ieSgpICU+JSBtdXRhdGUoKSAlPiUgdW5ncm91cCgpYCB5IHRyYWR1Y2lybG8gYSBTUUwuIAoKTmVjZXNpdGFtb3MgdXNhciBlc3RvIGN1YW5kbyBxdWVyZW1vcyBjYWxjdWxhciBsb3MgaW5kaWNhZG9yZXMgZGUgcmllc2dvIGRlIGxhIHJlZCBjb250cmF0aXN0YSAtIGZ1bmNpb25hcmlvLiBFbCB0aWVtcG8gZGUgZHVyYWNpw7NuIHkgZGluZXJvIGNvbnRyYXRhZG8gc2UgdmEgYWN1bXVsYW5kbywgZSBpbXBvcnRhIGVsIG9yZGVuLiBRdWVyZW1vcyBjb21wdXRhciB1biB2YWxvciBwYXJhIGNhZGEgaW5zdGFudGUgZGVsIHRpZW1wby4gQSBlc3RvIHNlIGxlIGNvbm9jZSBjb21vIGB3aW5kb3cgZnVuY3Rpb24uYAoK